home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2007 December
/
PCWKCD1207B.iso
/
Blogowanie poza sfera
/
Flock 1.0 beta
/
flock-1.0RC3.en-US.win32.exe
/
flock
/
components
/
flockFacebookAPI.js
< prev
next >
Wrap
Text File
|
2007-10-18
|
44KB
|
1,406 lines
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2007
// http://flock.com
//
// This file may be used under the terms of of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL
// Constants
const CC = Components.classes;
const CI = Components.interfaces;
const CR = Components.results;
const CU = Components.utils;
CU.import("resource:///modules/FlockCryptoHash.jsm");
const FACEBOOK_API_CID = Components.ID("{53b077f0-6155-11db-b0de-0800200c9a66}");
const FACEBOOK_API_CONTRACTID = "@flock.com/api/facebook;1";
// From developer.facebook.com...
const FACEBOOK_API_KEY = "7fa235c847b11f157e12586c86660f47";
const FACEBOOK_API_SECRET = "645e5d3f9fde64b65a1a12744486b093";
const FACEBOOK_API_VERSION = "1.0";
const FACEBOOK_API_HOSTNAME = "http://api.facebook.com/";
const FACEBOOK_API_ENDPOINT_URL = FACEBOOK_API_HOSTNAME + "restserver.php";
const FACEBOOK_API_TOS_URL = "http://www.facebook.com/tos.php?api_key="
+ FACEBOOK_API_KEY
+ "&v=1.0";
const FACEBOOK_API_SETSTATUS_URL = "http://www.facebook.com/updatestatus.php";
// From nsIXMLHttpRequest.idl
// 0: UNINITIALIZED open() has not been called yet.
// 1: LOADING send() has not been called yet.
// 2: LOADED send() has been called, headers and status are available.
// 3: INTERACTIVE Downloading, responseText holds the partial data.
// 4: COMPLETED Finished with all operations.
const XMLHTTPREQUEST_READYSTATE_UNINITIALIZED = 0;
const XMLHTTPREQUEST_READYSTATE_LOADING = 1;
const XMLHTTPREQUEST_READYSTATE_LOADED = 2;
const XMLHTTPREQUEST_READYSTATE_INTERACTIVE = 3;
const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;
const HTTP_CODE_OK = 200;
const HTTP_CODE_FOUND = 302;
const XMLHTTPREQUEST_CONTRACTID = "@mozilla.org/xmlextras/xmlhttprequest;1";
const FLOCK_ERROR_CONTRACTID = "@flock.com/error;1";
const FLOCK_PHOTO_ALBUM_CONTRACTID = "@flock.com/photo-album;1";
const FLOCK_SNOWMAN_URL = "chrome://browser/skin/flock/services/common/no_avatar.png";
function _getLoader() {
return CC["@mozilla.org/moz/jssubscript-loader;1"]
.getService(CI.mozIJSSubScriptLoader);
}
function loadLibraryFromSpec(aSpec) {
_getLoader().loadSubScript(aSpec);
}
loadLibraryFromSpec("chrome://browser/content/flock/photo/photoAPI.js");
function createEnum(array) {
return {
QueryInterface: function (iid) {
if (iid.equals(CI.nsISimpleEnumerator)) {
return this;
}
throw CR.NS_ERROR_NO_INTERFACE;
},
hasMoreElements: function() {
return (array.length>0);
},
getNext: function() {
return array.shift();
}
}
}
function MyBag() {
}
MyBag.prototype.setProperty =
function mybag_setProperty(aKey, aValue) {
this[aKey] = aValue;
};
MyBag.prototype.setPropertyAsInt32 =
function mybag_setPropertyAsInt32(aKey, aValue) {
this.setProperty(aKey, aValue);
};
MyBag.prototype.setPropertyAsAString =
function mybag_setPropertyAsString(aKey, aValue) {
this.setProperty(aKey, aValue);
};
MyBag.prototype.getProperty =
function mybag_getProperty(aKey) {
return this[aKey];
};
MyBag.prototype.getPropertyAsInt32 =
function mybag_getPropertyAsString(aKey) {
return this.getProperty(aKey);
};
MyBag.prototype.getPropertyAsAString =
function mybag_getPropertyAsString(aKey) {
return this.getProperty(aKey);
};
MyBag.prototype.QueryInterface =
function mybag_QueryInterface(aIid) {
return this;
};
function _validateUid(aUid) {
if (aUid.match(/[^0-9]/)) {
throw "Invalid UID '" + aUid + "': the UID must be a numeric value";
}
}
// ====================================================
// ========== BEGIN flockIPhoto object ==========
// ====================================================
function facebookPhoto() {
}
facebookPhoto.prototype = {
id: "",
thumbnail: "",
webPageUrl: "",
midSizePhoto: "",
largeSizePhoto: "",
title: "",
username: "",
userid: "",
is_public: true,
is_video: false,
svcShortName: "facebook",
buildTooltip: function facebookPhoto_buildTooltip() {
default xml namespace =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var tip =
<vbox>
<hbox>
<image src={this.midSizePhoto} style="margin-bottom: 2px;" />
<spacer flex="1" />
</hbox>
<hbox>
<image src={this.icon} />
<vbox style="max-width: 250px;">
<label crop="end">{this.title}</label>
<label class="user">{this.username}</label>
</vbox>
</hbox>
</vbox>;
var parser = CC["@mozilla.org/xmlextras/domparser;1"]
.createInstance(CI.nsIDOMParser);
var doc = parser.parseFromString(tip, "text/xml");
return doc.documentElement;
},
buildHTML: function facebookPhoto_buildHTML() {
var xml =
<a title={this.title} href={this.webPageUrl}>
<img src={this.midSizePhoto} border="0" />
</a>
return xml;
},
buildLargeHTML: function facebookPhoto_buildLargeHTML() {
var xml =
<a title={this.title} href={this.webPageUrl}>
<img src={this.largeSizePhoto} border="0" />
</a>
return xml;
},
buildBBCode: function facebookPhoto_buildBBCode() {
return '[url=' + this.webPageUrl + '][img]'+ this.largeSizePhoto +'[/img][/url]';
},
QueryInterface: function facebookPhoto_QueryInterface(aIid) {
if (!aIid.equals(CI.nsISupports) &&
!aIid.equals(CI.flockIPhoto)) {
throw CR.NS_ERROR_NO_INTERFACE;
}
return this;
}
};
/**
* Represents the Facebook API
*/
function facebookAPI() {
this.api_key = FACEBOOK_API_KEY;
this.api_secret = FACEBOOK_API_SECRET;
this.endpoint = FACEBOOK_API_ENDPOINT_URL;
this.prettyName = "Facebook";
this.uid = null;
this._friendsCache = {
value: null,
lastUpdate: new Date(0)
};
this.acUtils = CC["@flock.com/account-utils;1"]
.getService(CI.flockIAccountUtils);
var obService = CC["@mozilla.org/observer-service;1"]
.getService(CI.nsIObserverService);
var inst = this;
this.webDetective = CC["@flock.com/web-detective;1"]
.getService(CI.flockIWebDetective);
this._logger = CC["@flock.com/logger;1"].createInstance(CI.flockILogger);
this._logger.init("facebookAPI");
this.wrappedJSObject = this;
}
//****************************************
//* Authentication & Sesssion Calls
//****************************************/
facebookAPI.prototype.sessionPing =
function facebookAPI_sessionPing(aListener) {
var pingListener = {
onSuccess: function onSuccess(aResult) {
aListener.onSuccess(null, aResult.result);
},
onError: function onError(aError) {
aListener.onError(null, "", null);
}
}
this.authCallJSON(pingListener, "facebook.session.ping");
}
facebookAPI.prototype.authenticate =
function facebookAPI_authenticate(aListener) {
this._logger.debug("authenticate()");
var api = this;
var tokenListener = {
onSuccess: function(aToken) {
var authUrl = api.getAuthUrl(aToken);
var req = CC[XMLHTTPREQUEST_CONTRACTID].
createInstance(CI.nsIXMLHttpRequest);
// We try to authenticate in the normal fashion (apiAuth2) first.
// If that fails, it may be because we have yet to agree to the
// Facebook Platform User Terms of Service. apiAuth1 handles that case.
var onReadyStateFunc = function onReadyStateFunc(eEvt) {
if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
if (api.webDetective.detectNoDOM("facebook", "apiAuth2", "",
req.responseText, null))
{
api.getSession(aToken, aListener);
} else {
api._logger.debug("req: \n" + req.responseText);
var results = new MyBag();
if (api.webDetective.detectNoDOM("facebook",
"apiAuth1",
"",
req.responseText,
results))
{
var postBody = "grant_perm=1"
+ "&next="
+ "&api_key="
+ FACEBOOK_API_KEY
+ "&auth_token="
+ aToken
+ "&post_form_id="
+ results.getPropertyAsAString("formId");
var req2 = CC[XMLHTTPREQUEST_CONTRACTID].
createInstance(CI.nsIXMLHttpRequest);
req2.onreadystatechange = function (eEvt) {
if (req2.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
if (api.webDetective.detectNoDOM("facebook",
"apiAuth2",
"",
req2.responseText,
null))
{
api.getSession(aToken, aListener);
} else {
api._logger.debug(req.responseText);
var error = CC[FLOCK_ERROR_CONTRACTID]
.createInstance(CI.flockIError);
error.errorString = "apiAuth2 Failed.";
api._logger.debug("apiAuth2 Failed.");
aListener.onError(null, null, error);
}
}
}
req2.detachLoadGroup = true;
req2.open("POST", results.getPropertyAsAString("postUrl"), true);
req2.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
req2.overrideMimeType("text/txt");
req2.send(postBody);
} else {
var error = CC[FLOCK_ERROR_CONTRACTID]
.createInstance(CI.flockIError);
error.errorString = "apiAuth1 Failed.";
api._logger.debug("apiAuth1 Failed.");
aListener.onError(null, null, error);
}
}
}
}
req.onreadystatechange = onReadyStateFunc;
req.detachLoadGroup = true;
req.open("GET", authUrl, true);
req.overrideMimeType("text/txt");
req.send(null);
},
onError: function onError(aErrorCode) {
aListener.onError(null, "", aErrorCode);
}
};
this.createToken(tokenListener);
}
facebookAPI.prototype.deauthenticate =
function facebookAPI_deauthenticate() {
this._logger.debug(".deauthenticate()");
this.session_key = null;
this.secret = null;
this.uid = null;
this._friendsCache.value = null;
this.isLoggedIn = false;
}
// INTERNAL
facebookAPI.prototype.createToken =
function facebookAPI_createToken(aListener) {
this._logger.debug("createToken()\n");
var api = this;
var tokenListener = {
onSuccess: function onSuccess(aXml) {
api._logger.debug(".createToken().onSuccess " + aXml);
try {
var token = aXml.getElementsByTagName("token")[0].firstChild.nodeValue;
aListener.onSuccess(token);
} catch (err) {
try {
var token = aXml.getElementsByTagName("auth_createToken_response")[0]
.firstChild
.nodeValue;
aListener.onSuccess(token);
} catch (err) {
var error = CC[FLOCK_ERROR_CONTRACTID]
.createInstance(CI.flockIError);
error.errorString = err;
aListener.onError(null, "", error);
}
}
},
onError: function onError(aErrorCode) {
aListener.onError(null, "", aErrorCode);
}
};
this.call(tokenListener, "facebook.auth.createToken");
}
// INTERNAL
facebookAPI.prototype.getSession =
function facebookAPI_getSession(aToken, aListener) {
var api = this;
var aParams = {
auth_token: aToken
};
var sessionListener = {
onSuccess: function onSuccess(aXml) {
api.session_key = aXml.getElementsByTagName("session_key")[0]
.firstChild
.nodeValue;
api.secret = aXml.getElementsByTagName("secret")[0]
.firstChild
.nodeValue;
api.uid = aXml.getElementsByTagName("uid")[0]
.firstChild
.nodeValue;
api._friendsCache.value = null;
api.isLoggedIn = true;
aListener.onSuccess(null, "authenticated")
},
onError: function onError(aErrorCode) {
api.isLoggedIn = false;
aListener.onError(null, "", aErrorCode);
}
};
api.isLoggedIn = false;
api.call(sessionListener, "facebook.auth.getSession", aParams);
}
// INTERNAL
facebookAPI.prototype.getAuthUrl =
function facebookAPI_getAuthUrl(aToken) {
return "http://www.facebook.com/login.php?api_key=" + this.api_key
+ "&v=" + FACEBOOK_API_VERSION + "&auth_token=" + aToken;
}
facebookAPI.prototype.getFacebookURL =
function facebookAPI_getFacebookURL(aUrlType, aFriendId) {
switch (aUrlType) {
case "profile":
return "http://www.facebook.com/profile.php?uid="
+ aFriendId
+ "&api_key="
+ this.api_key;
break;
case "myprofile":
return "http://www.facebook.com/profile.php?id="
+ aFriendId
+ "&api_key="
+ this.api_key;
break;
case "poke":
return "http://www.facebook.com/poke.php?uid="
+ aFriendId
+ "&api_key="
+ this.api_key;
break;
case "message":
return "http://www.facebook.com/message.php?uid="
+ aFriendId
+ "&api_key="
+ this.api_key;
break;
case "editprofile":
return "http://www.facebook.com/editprofile.php";
break;
case "photos":
return "http://www.facebook.com/photo_search.php?uid="
+ aFriendId
+ "&api_key="
+ this.api_key;
break;
case "userphotos":
return "http://www.facebook.com/photo_search.php?uid="
+ aFriendId
+ "&api_key="
+ this.api_key;
break;
case "myphotos":
return "http://www.facebook.com/photos.php?id="
+ aFriendId;
break;
case "postwall":
return "http://www.facebook.com/wallpost.php?uid="
+ aFriendId
+ "&api_key="
+ this.api_key;
break;
case "addfriend":
return "http://www.facebook.com/addfriend.php?uid="
+ aFriendId
+ "&api_key="
+ this.api_key;
break;
case "friendrequests":
return "http://www.facebook.com/reqs.php";
break;
case "messages":
return "http://www.facebook.com/mailbox.php";
break;
case "homepage":
return "http://www.facebook.com/home.php";
break;
}
return "";
}
//****************************************
//* Friends Calls
//****************************************/
facebookAPI.prototype.friendsGet =
function facebookAPI_friendsGet(aListener) {
var api = this;
var listener = {
onSuccess: function listener_onSuccess(aResult) {
var peeps = {};
for (var i in aResult) {
var uid = aResult[i].uid;
peeps[uid] = aResult[i];
if (!peeps[uid].pic_square) {
peeps[uid].pic_square = "";
}
if (!peeps[uid].status) {
peeps[uid].status = {
time: 0,
message: ""
}
}
api._logger.debug("Got facebook person named "
+ peeps[uid].name);
}
var result = {
wrappedJS: peeps
};
aListener.onSuccess(result, "success");
},
onError: function(errorCode) {
api._logger.debug(".friendsGet() error: " + errorCode);
}
}
// For security let's make sure this.uid is what
// it's supposed to be: just a number
_validateUid(this.uid);
var friendsQuery = "SELECT uid,name,pic_square,status,profile_update_time "
+ "FROM user "
+ "WHERE uid IN (SELECT uid2 FROM friend WHERE uid1 = "+this.uid+")";
this.authCallJSON(listener, "facebook.fql.query", { query: friendsQuery });
}
facebookAPI.prototype.getUpdatedMediaFriends =
function facebookAPI_getUpdatedMediaFriends(aListener, aLastSeen) {
var api = this;
var listener = {
onSuccess: function gumfListener_onSuccess(aResult) {
// We put everything in a hash first to avoid duplicates
var friends = {};
for (var i in aResult) {
var owner = aResult[i]["owner"];
// 10 for Base 10
var created = parseInt(aResult[i]["created"], 10);
if (friends[owner]) {
friends[owner].count++;
if (created > friends[owner].latest) {
friends[owner].latest = created;
}
} else {
friends[owner] = {
count: 1,
latest: created
};
}
}
var result = [];
for (i in friends) {
var person = new MyBag();
person.setPropertyAsAString("uid", i);
person.setPropertyAsInt32("count", friends[i].count);
person.setPropertyAsInt32("latest", friends[i].latest);
result.push(person);
}
aListener.onSuccess(createEnum(result), "success");
},
onError: function gumfListener_onError(errorCode) {
api._logger.debug(".getUpdatedMediaFriends() error: " + errorCode);
}
}
// For security let's make sure this.uid is what
// it's supposed to be: just a number
_validateUid(this.uid);
var friendsQuery = "SELECT uid1 "
+ "FROM friend "
+ "WHERE uid2=" + this.uid;
var albumQuery = "SELECT aid "
+ "FROM album "
+ "WHERE owner IN " + "(" + friendsQuery + ")";
var query = "SELECT owner,created "
+ "FROM photo "
+ "WHERE created > " + aLastSeen
+ " AND " + "aid IN (" + albumQuery + ")";
this.authCallJSON(listener, "facebook.fql.query", { query: query });
};
//****************************************
//* Users Info Call
//****************************************/
facebookAPI.prototype.usersGetInfo =
function facebookAPI_usersGetInfo(aListener, users, fields) {
var params = {
uids: users,
fields: "name,pic_square,status"
}
if (fields) {
this._logger.debug("WARNING - arbitrary fields are not handled yet");
params.fields = fields;
}
var api = this;
var listener = {
onSuccess: function ugiListener_onSuccess(aResult) {
var peeps = [];
for (var i in aResult) {
var user = aResult[i];
var person = new MyBag();
person.setPropertyAsAString("uid", user.uid);
person.setPropertyAsAString("name", user.name);
person.setPropertyAsAString("avatar", user.pic_square);
if (user.status) {
person.setPropertyAsAString("statusMessage",
user.status.message);
}
peeps.push(person);
}
aListener.onSuccess(createEnum(peeps), "success");
},
onError: function ugiListener_onError(aError) {
api._logger.debug(".usersGetInfo() error:" + aError.errorString);
}
}
this.authCallJSON(listener, "facebook.users.getInfo", params);
}
facebookAPI.prototype.notificationsGet =
function facebookAPI_notificationsGet(aListener) {
// FIXME: Use JSON or at least E4X (bug 9573)
var api = this;
var listener = {
onSuccess: function ngListener_onSuccess(aXml) {
var notifications = [];
var meNotifications = new MyBag();
meNotifications.setPropertyAsAString("messages",
aXml.getElementsByTagName("messages")[0]
.getElementsByTagName("unread")[0]
.childNodes[0]
.nodeValue);
meNotifications.setPropertyAsAString("pokes",
aXml.getElementsByTagName("pokes")[0]
.getElementsByTagName("unread")[0]
.firstChild
.nodeValue);
meNotifications.setPropertyAsAString("friendRequests",
aXml.getElementsByTagName("friend_requests")[0]
.getElementsByTagName("uid")
.length);
meNotifications.setPropertyAsAString("groupInvites",
aXml.getElementsByTagName("group_invites")[0]
.getElementsByTagName("gid")
.length);
meNotifications.setPropertyAsAString("eventInvites",
aXml.getElementsByTagName("event_invites")[0]
.getElementsByTagName("eid")
.length);
notifications.push(meNotifications);
aListener.onSuccess(createEnum(notifications), "success");
},
onError: function ngListener_onError(aError) {
api._logger.debug(".notificationsGet() error:" + aError.errorString);
}
};
this.authCall(listener, "facebook.notifications.get");
}
facebookAPI.prototype.setStatus =
function facebookAPI_setStatus(aNewStatus, aListener) {
var api = this;
function reallySetStatus(aPostFormId) {
var params = {
post_form_id: aPostFormId
};
if (aNewStatus && aNewStatus != "") {
params.status = aNewStatus;
} else {
params.clear = "1";
}
var body = api.getParamString(params);
var xhr2 = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(CI.nsIXMLHttpRequest);
xhr2.open ("POST", FACEBOOK_API_SETSTATUS_URL, true);
xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr2.setRequestHeader("Content-Length", body.length);
xhr2.onreadystatechange = function (aEvent) {
if (xhr2.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
if ((xhr2.status != HTTP_CODE_OK) &&
(xhr2.status != HTTP_CODE_FOUND))
{
if (aListener) {
aListener.onError(xhr2.statusText);
}
return;
}
if (aListener) {
aListener.onSuccess(null, "");
}
}
}
xhr2.send(body);
}
function viewTOS() {
// Show the TOS and to get the form
var xhr = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(CI.nsIXMLHttpRequest);
xhr.open("GET", FACEBOOK_API_TOS_URL, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function (aEvent) {
if (xhr.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
if (xhr.status != HTTP_CODE_OK) {
if (aListener) {
aListener.onError (xhr.statusText);
}
return;
}
// Get the post_form_id and setStatus for real
var postFormId;
var results = CC["@mozilla.org/hash-property-bag;1"]
.createInstance(CI.nsIWritablePropertyBag2);
try {
if (api.webDetective.detectNoDOM("facebook",
"setStatus",
"",
xhr.responseText,
results))
{
postFormId = results.getPropertyAsAString("status");
reallySetStatus(postFormId);
}
} catch (ex) {
aListener.onError();
}
}
}
xhr.send(null);
}
viewTOS();
}
//****************************************
//* Photo Call
//****************************************/
facebookAPI.prototype.getAlbums =
function facebookAPI_getAlbums(aListener, userID) {
var api = this;
var listener = {
onSuccess: function(xml) {
var albums = [];
var eAlbums = xml.getElementsByTagName('album')
for (var i=0; i<eAlbums.length; i++) {
var id = eAlbums[i].getElementsByTagName('aid')[0]
.firstChild
.nodeValue;
var title = eAlbums[i].getElementsByTagName('name')[0]
.firstChild
.nodeValue;
var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
.createInstance(CI.flockIPhotoAlbum);
newAlbum.id = id;
newAlbum.title = title;
api._logger.debug(".getAlbums() Found album: title = " + title +
", id = " + id);
albums.push(newAlbum);
}
aListener.onSuccess(createEnum(albums), 'success');
},
onError: function(aFlockError) {
api._logger.debug(".getAlbums() error: " + aFlockError.errorString);
aListener.onError(null, null, aFlockError);
}
}
var params = {
uid: userID
}
this.authCall(listener, 'facebook.photos.getAlbums', params);
}
facebookAPI.prototype.createAlbum =
function facebookAPI_createAlbum(aListener, aAlbumTitle) {
var api = this;
var listener = {
onSuccess: function ca_onSuccess(aXml) {
var id = aXml.getElementsByTagName("aid")[0].firstChild.nodeValue;
var title = aXml.getElementsByTagName("name")[0].firstChild.nodeValue;
var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
.createInstance(CI.flockIPhotoAlbum);
newAlbum.title = title;
newAlbum.id = id;
aListener.onSuccess(newAlbum, "success");
},
onError: function ca_onError(aFlockError) {
api._logger.debug(".createAlbum() error: " + aFlockError.errorString);
aListener.onError(aFlockError);
}
}
var params = {
name: aAlbumTitle
}
this.authCall(listener, "facebook.photos.createAlbum", params);
}
facebookAPI.prototype.getPhotos =
function facebookAPI_getPhotos(aListener, aSubjectId, aAlbumId, aPidList) {
var api = this;
var listener = {
onSuccess: function gp_onSuccess(aResult) {
var photos = [];
for (var i in aResult) {
var photo = aResult[i];
var newPhoto = new facebookPhoto();
newPhoto.id = photo.pid;
newPhoto.webPageUrl = photo.link;
newPhoto.thumbnail = photo.src_small;
newPhoto.midSizePhoto = photo.src;
newPhoto.largeSizePhoto = photo.src_big;
newPhoto.userid = photo.owner;
newPhoto.title = photo.caption ? photo.caption : "";
newPhoto.uploadDate = parseInt(photo.created) * 1000;
// FIXME: need to get the full name and avatar of user - DP
newPhoto.username = photo.owner;
newPhoto.icon = FLOCK_SNOWMAN_URL;
photos.push(newPhoto);
}
aListener.onSuccess(createEnum(photos), 'success');
},
onError: function gp_onError(flockError) {
api._logger.debug(".getPhotos() error: " + flockError.errorString);
aListener.onError(null, null, flockError);
}
};
var params = {};
if (aSubjectId) {
params.subj_id = aSubjectId;
}
if (aAlbumId) {
params.aid = aAlbumId;
}
if (aPidList) {
params.pids = aPidList;
}
this.authCallJSON(listener, "facebook.photos.get", params);
}
facebookAPI.prototype._finalizePhoto =
function facebookAPI__finalizePhoto(aUploadListener,
aUpload,
aId)
{
var getPhotoListener = {
onSuccess: function fp_onSuccess(aPhotos) {
if (aPhotos.hasMoreElements()) {
var photo = aPhotos.getNext();
photo.QueryInterface(CI.flockIPhoto);
aUploadListener.onUploadFinalized(aUpload, photo);
} else {
// Empty result
aUploadListener.onError(null);
}
},
onError: function fp_onError(aError) {
aUploadListener.onError(null);
}
};
this.getPhotos(getPhotoListener, null, null, aId);
}
facebookAPI.prototype.uploadPhotosViaUploader =
function facebookAPI_uploadPhotosViaUploader(aListener,
aPhoto,
aParams,
aUpload) {
var api = this;
var pid;
var listener = {
onResult: function listener_onResult(aXml) {
api._logger.debug(CC["@mozilla.org/xmlextras/xmlserializer;1"]
.getService(CI.nsIDOMSerializer)
.serializeToString(aXml));
var resp = aXml.getElementsByTagName("photos_upload_response")[0];
if (!resp) {
var error = api.getXMLError(aXml);
aListener.onError(error);
} else {
pid = aXml.getElementsByTagName("pid")[0].childNodes[0].nodeValue;
// tag photo if user requested
if (aUpload.notes) {
var addTagListener = {
onSuccess: function addTagListener_success(aXml, aStatus) {
api._logger.debug("tagged successfully\n");
},
onError: function addTagListener_error(aSubject, aTopic, aError) {
api._logger.error("error tagging photo\n");
}
};
api.addTag(addTagListener, pid, "", "", "", "", aUpload.notes);
}
aListener.onUploadComplete(aUpload);
api._finalizePhoto(aListener, aUpload, pid);
}
},
onError: function listener_onError(aErrorCode) {
if (aErrorCode) {
aListener.onError(api.getHTTPError(aErrorCode));
} else {
aListener.onError(api.getError(null, null));
}
},
onProgress: function listener_onProgress(aCurrentProgress) {
aListener.onProgress(aCurrentProgress);
}
};
var params = {};
params.method = "facebook.photos.upload";
params.api_key = this.api_key;
params.session_key = this.session_key;
params.call_id = new Date().getTime();
params.v = FACEBOOK_API_VERSION;
params.aid = aUpload.album;
params.caption = aParams.getProperty("description");
var uploader = new PhotoUploader();
params = api.appendSignature(params, api.secret);
uploader.setEndpoint(FACEBOOK_API_ENDPOINT_URL);
uploader.upload(listener, aPhoto, params);
return;
}
facebookAPI.prototype.addTag =
function facebookAPI_addTag(aListener, aPid, aTagUid, aTagText, aX, aY, aTags) {
this._logger.debug(".addTag()");
var json;
if (aTags) {
json = aTags;
} else {
json = '[{"x":"' + aX + '","y":"' + aY + '"';
if (aTagUid) {
json += ',"tag_uid":' + aTagUid;
} else if (aTagText) {
json += ',"tag_text":"' + aTagText + '"';
}
json += '}]';
}
this._logger.debug("tag json: " + json);
var myListener = {
onSuccess : function(xml) {
aListener.onSuccess(xml, "success");
},
onError: function(aFlockError) {
aListener.onError(null, null, aFlockError);
}
};
var params = {
pid: aPid,
tags: json
};
this.authCall(myListener, "facebook.photos.addTag", params);
}
//****************************************
//* FQL Query calls
//****************************************/
facebookAPI.prototype.doFQLQuery=
function facebookAPI_doFQLQuery(aListener, aFQLQuery) {
var myListener = {
onSuccess : function(xml) {
aListener.onSuccess(xml, "success");
},
onError: function(aFlockError) {
aListener.onError(null, null, aFlockError);
}
};
var params = {
format: "xml",
query: aFQLQuery
};
this.authCall(myListener, 'facebook.fql.query', params);
}
//******************************************
//* internal functions for making requests
//******************************************
/* --- authCall is used for authenticated (most) calls --- */
facebookAPI.prototype.authCall =
function facebookAPI_authCall(aListener, aMethod, aParams, aIsDryRun) {
if (!aParams) aParams = {};
aParams.api_key = this.api_key;
aParams.session_key = this.session_key;
aParams.call_id = new Date().getTime();
aParams.method = aMethod;
aParams.v = FACEBOOK_API_VERSION;
var paramString = this.getParamString(aParams, this.secret);
var url = this.endpoint + "?" + paramString;
this._logger.debug("===[" + aMethod + "]===> " + url);
if (!aIsDryRun) {
this._doCall(aListener, url, null);
}
}
facebookAPI.prototype.authCallJSON =
function facebookAPI_authCallJSON(aListener, aMethod, aParams, aIsDryRun) {
if (!aParams) aParams = {};
aParams.api_key = this.api_key;
aParams.session_key = this.session_key;
aParams.call_id = new Date().getTime();
aParams.method = aMethod;
aParams.format = "JSON";
aParams.v = FACEBOOK_API_VERSION;
var paramString = this.getParamString(aParams, this.secret);
var url = this.endpoint + "?" + paramString;
this._logger.debug("===[" + aMethod + "]===> " + url);
if (!aIsDryRun) {
this._doCallJSON(aListener, url, null);
}
}
/* --- call is only used to start authentication by getting a token --- */
facebookAPI.prototype.call =
function facebookAPI_call(aListener, aMethod, aParams, aIsDryRun) {
if (!aParams) aParams = {};
aParams.api_key = this.api_key;
aParams.method = aMethod;
aParams.v = FACEBOOK_API_VERSION;
var paramString = this.getParamString(aParams, this.api_secret);
var url = this.endpoint + "?" + paramString;
this._logger.debug("===[" + aMethod + "]===> " + url);
if (!aIsDryRun) {
this._doCall(aListener, url, null);
}
}
/* --- convert dictionary to a request string, adding the signature --- */
facebookAPI.prototype.getParamString =
function facebookAPI_getParamString(aParams, secret) {
var params = this.appendSignature(aParams, secret);
var rval = "";
var count = 0;
for(var p in params) {
if(count++!=0) rval += "&";
rval += encodeURIComponent(p) + "=" + encodeURIComponent(params[p]);
}
return rval;
};
/* --- calculate and add the signature to the request parameters --- */
facebookAPI.prototype.appendSignature =
function facebookAPI_appendSignature(aParams, secret) {
var keys = new Array();
for (var p in aParams) keys.push(p);
keys.sort();
var preHash = '';
for (var i=0; i<keys.length; ++i) preHash += keys[i] + '=' + aParams[keys[i]];
preHash += secret;
var converter = CC["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(CI.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var inputStream = converter.convertToInputStream(preHash);
var newParams = aParams;
newParams.sig = FlockCryptoHash.md5Stream(inputStream);
return newParams;
};
facebookAPI.prototype.getHTTPError =
function facebookAPI_getHTTPError(aHTTPErrorCode) {
var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
error.errorCode = aHTTPErrorCode;
return error;
}
facebookAPI.prototype.getXMLError =
function facebookAPI_getXMLError(aXML) {
// FIXME: Use E4X (bug 9573)
var fbErrorCode;
var fbErrorMessage;
// <error_response xmlns="http://api.facebook.com/1.0/">
// <error_code>324</error_code>
// <error_msg>Missing or invalid image file</error_msg>
// <request_args/>
// </error_response>
try {
fbErrorCode = aXML.getElementsByTagName("error_code")[0]
.childNodes[0].nodeValue;
fbErrorMessage = aXML.getElementsByTagName("error_msg")[0]
.childNodes[0].nodeValue;
} catch (ex) {
// <result method="" type="struct">
// <fb_error type="struct">
// <code>101</code>
// <msg>Invalid API key</msg>
// <your_request/>
// </fb_error>
// </result>
try {
fbErrorCode = aXML.getElementsByTagName("code")[0]
.childNodes[0].nodeValue;
fbErrorMessage = aXML.getElementsByTagName("msg")[0]
.childNodes[0].nodeValue;
} catch (ex2) {
// in case the error xml is invalid
fbErrorCode = "999";
}
}
return this.getError(fbErrorCode, fbErrorMessage);
}
facebookAPI.prototype.getError =
function facebookAPI_getError(aFBErrorCode, aFBErrorMessage) {
var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
this._logger.debug(".getError() Facebook error code: " + aFBErrorCode + "\n");
switch (aFBErrorCode) {
case "2":
// 2: The service is not available at this time.
error.errorCode = CI.flockIError.PHOTOSERVICE_UNAVAILABLE;
break;
case "100":
// 100: One of the parameters specified was missing or invalid.
case "103":
// 103: The submitted call_id was not greater than the previous call_id
// for this session.
case "104":
// 104: Incorrect signature.
error.errorCode = CI.flockIError.PHOTOSERVICE_INVALID_QUERY;
break;
case "101":
// 101: The api key submitted is not associated with any known
// application.
error.errorCode = CI.flockIError.PHOTOSERVICE_INVALID_API_KEY;
break;
case "102":
// 102: The session key was improperly submitted or has reached its
// timeout. Direct the user to log in again to obtain another key.
error.errorCode = CI.flockIError.PHOTOSERVICE_USER_NOT_LOGGED_IN;
error.serviceName = this.prettyName;
break;
case "321":
// 321: Album is full
error.errorCode = CI.flockIError
.PHOTOSERVICE_PHOTOS_IN_ALBUM_LIMIT_REACHED;
break;
case "1":
// 1: An unknown error occurred. Please resubmit the request.
case "999":
error.errorCode = CI.flockIError.PHOTOSERVICE_UNKNOWN_ERROR;
break;
default:
error.errorCode = CI.flockIError.PHOTOSERVICE_UNKNOWN_ERROR;
error.serviceErrorString = serviceErrorMessage;
}
return error;
}
/* --- actually make the http request --- */
facebookAPI.prototype._doCall =
function facebookAPI_doCall(aListener, aUrl, aContent) {
var api=this;
// copied from flickr service --- potential issues:
// 1) shouldn't be using xmlhttprequest
// 2) keeping a reference to the request via this.req
// keeps it referenced after we are done with it
// (until the next call)
this.req = CC[XMLHTTPREQUEST_CONTRACTID].
createInstance(CI.nsIXMLHttpRequest);
this.req.QueryInterface(CI.nsIJSXMLHttpRequest);
this.req.open("GET", aUrl, true);
var req = this.req;
this.req.onreadystatechange = function (aEvt) {
api._logger.debug("._doCall() ReadyState = " + req.readyState);
if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
try {
api._logger.debug("._doCall() Status = " + req.status);
if(req.status/100 == 2) {
try {
api._logger.debug("._doCall() response = \n" + req.responseText);
var fb_error = req.responseXML
.getElementsByTagName("error_response");
if (fb_error.length > 0) {
api._logger.debug("._doCall() Error");
aListener.onError(api.getXMLError(req.responseXML));
} else {
api._logger.debug("._doCall() Success");
aListener.onSuccess(req.responseXML);
}
} catch (ex) {
// error parsing xml
api._logger.error("._doCall() Parse error = " + ex);
aListener.onError(api.getError(null, null));
}
} else {
// http errors
api._logger.debug("._doCall() HTTP Error");
aListener.onError(api.getHTTPError(req.status));
}
} catch (ex) {
// XMLHTTPERROR (connection lost)
api._logger.debug(".doCall() " + ex);
aListener.onError(api.getHTTPError("9001"));
}
}
}
try {
// just for debug output
aUrl.match(/call_id=(.+)&/);
var call_id = RegExp.$1;
this._logger.debug("sending call_id: " + call_id);
} catch (ex) {
}
this.req.send(null);
};
facebookAPI.prototype._doCallJSON =
function facebookAPI_doCallJSON(aListener, aUrl, aContent) {
var api = this;
this.req = CC[XMLHTTPREQUEST_CONTRACTID]
.createInstance(CI.nsIXMLHttpRequest);
this.req.QueryInterface(CI.nsIJSXMLHttpRequest);
this.req.open("GET", aUrl, true);
var req = this.req;
this.req.onreadystatechange =
function doCallJSON_onreadystatechange(aEvt) {
api._logger.debug("._doCall() ReadyState = " + req.readyState);
if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
try {
api._logger.debug("._doCall() Status = " + req.status);
if (req.status/100 == 2) {
api._logger.debug("._doCall() response = \n" + req.responseText);
var s = new Components.utils.Sandbox("about:blank");
var result = Components.utils.evalInSandbox(req.responseText, s);
if (result && result.error_code && result.error_msg) {
api._logger.debug("._doCall() Error");
aListener.onError(api.getError(result.error_code,
result.error_msg));
} else {
api._logger.debug("._doCall() Success");
aListener.onSuccess(result);
}
} else {
// http errors
api._logger.debug("._doCall() HTTP Error");
aListener.onError(api.getHTTPError(req.status));
}
} catch (ex) {
// XMLHTTPERROR (connection lost)
api._logger.debug(".doCall() " + ex);
aListener.onError(api.getHTTPError("9001"));
}
}
}
try {
// just for debug output
aUrl.match(/call_id=(.+)&/);
var call_id = RegExp.$1;
this._logger.debug("sending call_id: " + call_id);
} catch (ex) {
}
this.req.send(null);
};
//*****************************************************
//* xpcom constructor/info
//*****************************************************
facebookAPI.prototype.flags = CI.nsIClassInfo.SINGLETON;
facebookAPI.prototype.classDescription = "Facebook JS API";
facebookAPI.prototype.getInterfaces = function (count) {
var interfaceList = [
CI.flockIFacebookAPI,
CI.nsIClassInfo
];
count.value = interfaceList.length;
return interfaceList;
}
facebookAPI.prototype.getHelperForLanguage = function (count) { return null; }
facebookAPI.prototype.QueryInterface = function (iid) {
if (!iid.equals(CI.flockIFacebookAPI) &&
!iid.equals(CI.nsIClassInfo) &&
!iid.equals(CI.nsISupports)) {
throw CR.NS_ERROR_NO_INTERFACE;
}
return this;
}
facebookAPI.prototype.createPhoto = function () {
return (new facebookPhoto().QueryInterface(CI.flockIPhoto));
}
var FB_API_Module = new Object();
FB_API_Module.registerSelf = function (compMgr, fileSpec, location, type) {
compMgr = compMgr.QueryInterface(CI.nsIComponentRegistrar);
compMgr.registerFactoryLocation(FACEBOOK_API_CID,
"Flock Facebook API JS Component",
FACEBOOK_API_CONTRACTID,
fileSpec,
location,
type);
}
FB_API_Module.getClassObject = function (compMgr, cid, iid) {
if (!cid.equals(FACEBOOK_API_CID)) {
throw CR.NS_ERROR_NO_INTERFACE;
}
if (!iid.equals(CI.nsIFactory)) {
throw CR.NS_ERROR_NOT_IMPLEMENTED;
}
return FB_API_ServiceFactory;
}
FB_API_Module.canUnload = function (compMgr) {
return true;
}
/* factory object */
var FB_API_ServiceFactory = new Object();
FB_API_ServiceFactory.createInstance = function (outer, iid) {
if (outer != null) {
throw CR.NS_ERROR_NO_AGGREGATION;
}
return (new facebookAPI()).QueryInterface(iid);
}
/* entrypoint */
function NSGetModule(compMgr, fileSpec) {
return FB_API_Module;
}